package com.arpa.ntocc.common.security.realm;

import cn.hutool.crypto.SmUtil;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CreateCache;
import com.arpa.ntocc.common.domain.entity.Party;
import com.arpa.ntocc.common.domain.entity.UserLogin;
import com.arpa.ntocc.common.domain.enums.PartyStatusEnum;
import com.arpa.ntocc.common.service.IPartyService;
import com.arpa.ntocc.common.service.IUserLoginService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.PostConstruct;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 无状态 realm
 * @author leo
 */
public class StatelessAuthorizingRealm extends AuthorizingRealm {

    /**
     * 登录失败次数限制，可根据实际情况改为配置
     */
    private static final int ERROR_COUNT = 5;

    /**
     * 账号登录失败缓存，默认5分钟失效时间
     */
    @CreateCache(name = "USER_LOGIN_FAIL_",expire = 5,timeUnit = TimeUnit.MINUTES)
    private Cache<String, AtomicInteger> userLoginFailCache;

    @Autowired
    private IUserLoginService userLoginService;

    /**
     * 用户账户信息service
     */
    @Autowired
    private IPartyService partyService;
    /**
     * 认证回调函数,登录时调用.
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
            throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
        String userLoginId = token.getUsername();
        UserLogin userLogin = userLoginService.getUserLoginByUserLoginId(userLoginId);
        if (userLogin != null) {
            //获取用户信息
            Party party = partyService.getByCode(userLogin.getPartyCode());
            // 帐号禁用
            if (Objects.equals(party.getStatus(), PartyStatusEnum.DISABLED)) {
                throw new DisabledAccountException();
            }
            return new SimpleAuthenticationInfo(userLoginId, userLogin.getCurrentPassword(), getName());
        } else {
            //用户不存在
            throw new UnknownAccountException();
        }
    }

    /**
     * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        return info;
    }

    /**
     * 设定Password校验的Hash算法与迭代次数.
     */
    @PostConstruct
    public void initCredentialsMatcher() {
        //登陆失败次数限制
        SM3CredentialsMatcher sm3CredentialsMatcher = new SM3CredentialsMatcher();
        setCredentialsMatcher(sm3CredentialsMatcher);
    }

    /**
     * 国密3加密算法Matcher
     */
    public class SM3CredentialsMatcher extends SimpleCredentialsMatcher {
        @Override
        public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) {
            UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
            String loginId = token.getUsername();
            AtomicInteger retryCount = userLoginFailCache.get(loginId);
            if(retryCount == null) {
                retryCount = new AtomicInteger(0);
                userLoginFailCache.put(loginId, retryCount);
            }
            //密码输错超过5次，锁定
            if(retryCount.incrementAndGet() > ERROR_COUNT) {
                throw new ExcessiveAttemptsException("登陆错误次数过多，账号已锁定，请5分钟后再试!");
            } else {
                userLoginFailCache.put(loginId, retryCount);
            }
            UserLogin userLogin = userLoginService.getUserLoginByUserLoginId(loginId);
            String salt = userLogin.getSalt();
            Object tokenCredentials = SmUtil.sm3(new String(token.getPassword())+salt);
            Object accountCredentials = getCredentials(info);
            // 将密码加密与系统加密后的密码校验，内容一致就返回true,不一致就返回false
            boolean matches = equals(tokenCredentials, accountCredentials);
            if(matches) {
                //登录成功清空失败次数
                userLoginFailCache.remove(loginId);
            }
            return  matches;
        }
    }

}
